package main

import (
	"fmt"
	"gitee.com/binny_w/go-util"
	"github.com/gin-gonic/gin"
	"go_slide_verify/captcha"
	"io"
	"math"
	"net/http"
	"strconv"
	"time"
)

var cache = util.NewMemCache()

type Data struct {
	UA   string
	Ip   string
	X    int
	Code string
}

type Resp struct {
	Code    int    `json:"code"`
	Msg     string `json:"msg"`
	Payload any    `json:"payload"`
}

func RespSuccess(c *gin.Context, payload any) {
	c.JSON(http.StatusOK, &Resp{
		Code:    http.StatusOK,
		Msg:     "",
		Payload: payload,
	})
}

func RespError(c *gin.Context, code int, msg string) {
	c.JSON(http.StatusOK, &Resp{
		Code: code,
		Msg:  msg,
	})
}

var rateLimit = util.NewRateLimit(30, 10*time.Second)

func main() {
	gin.SetMode(gin.ReleaseMode)
	gin.DefaultWriter = io.Discard
	engine := gin.Default()

	engine.Use(func(c *gin.Context) {
		origin := c.Request.Header.Get("Origin")
		if origin == "" {
			origin = "*"
		}
		c.Header("Access-Control-Allow-Origin", origin)
		c.Header("Access-Control-Allow-Headers", "X-Requested-With")
		c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT")
		c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
		c.Header("Access-Control-Allow-Credentials", "true")
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
		}
		c.Next()
	}, func(c *gin.Context) {
		if rateLimit.Check() {
			c.Next()
		} else {
			c.AbortWithStatus(http.StatusTooManyRequests)
		}
	})

	api := engine.Group(`/api`)
	{
		api.GET(`get`, func(c *gin.Context) {
			ret, err := captcha.Generate()
			if err != nil {
				_ = c.AbortWithError(555, err)
				return
			}
			id := fmt.Sprintf("%d%d", time.Now().UnixNano(), util.RandRange(1000, 10000))
			cache.Set(id, &Data{
				UA: c.Request.UserAgent(),
				Ip: c.ClientIP(),
				X:  ret.Point.X,
			}, 30)
			RespSuccess(c, gin.H{
				`id`:  id,
				`bg`:  ret.BackgroundImg,
				`blk`: ret.BlockImg,
				`y`:   ret.Point.Y,
			})
		})
		api.POST(`check`, func(c *gin.Context) {
			var id string
			var x int
			var err error
			var data *Data
			var dataAny any
			var ok bool
			if x, err = strconv.Atoi(c.PostForm(`x`)); err != nil {
				RespError(c, http.StatusBadRequest, `错误的拼图`)
				return
			}
			id = c.PostForm(`id`)
			if id == `` {
				RespError(c, http.StatusBadRequest, `错误的 id`)
				return
			}
			if dataAny, err = cache.Get(id); err != nil {
				RespError(c, http.StatusBadRequest, `错误的 id`)
				return
			}
			if data, ok = dataAny.(*Data); !ok {
				cache.Del(id)
				RespError(c, http.StatusBadRequest, `错误的 id`)
				return
			}
			if data.Code != `` || data.UA != c.Request.UserAgent() || data.Ip != c.ClientIP() {
				cache.Del(id)
				RespError(c, http.StatusBadRequest, `错误的请求`)
				return
			}
			if math.Abs(float64(x-data.X)) > 5 {
				cache.Del(id)
				RespError(c, http.StatusBadRequest, `错误的拼图`)
				return
			}
			data.Code = util.StrRandom(8, ``)
			RespSuccess(c, gin.H{
				`id`:   id,
				`code`: data.Code,
			})
		})
		api.POST(`submit`, func(c *gin.Context) {
			var id string
			var err error
			var data *Data
			var dataAny any
			var ok bool
			id = c.PostForm(`id`)
			if id == `` {
				RespError(c, http.StatusBadRequest, `错误的 id`)
				return
			}
			if dataAny, err = cache.Get(id); err != nil {
				RespError(c, http.StatusBadRequest, `错误的 id`)
				return
			}
			defer cache.Del(id)
			if data, ok = dataAny.(*Data); !ok {
				RespError(c, http.StatusBadRequest, `错误的 id`)
				return
			}
			if data.Code != c.PostForm(`code`) || data.UA != c.Request.UserAgent() || data.Ip != c.ClientIP() {
				RespError(c, http.StatusBadRequest, `错误的请求`)
				return
			}
			RespSuccess(c, nil)
		})
	}
	util.HttpSrvRun(`localhost:6776`, engine, nil)
}
